// This is unpublished source code. Michah Lerner 2006

package trieMatch.consoleApps;

// Non-threaded driver for keymatches.

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

import trieMatch.Constants;
import trieMatch.Interfaces.ItemProcessorIF;
import trieMatch.keywordMatcher.KMDefinition;
import trieMatch.keywordMatcher.KeyMatch;
import trieMatch.keywordMatcher.ResultFormatter;
import trieMatch.keywordMatcher.Tiers;
import trieMatch.keywordMatcher.TrieMatcher;
import trieMatch.keywordMatcher.KeyMatch.EvalType;
import trieMatch.keywordMatcher.KeyMatch.MatcherActionDefinition;
import trieMatch.util.FileProcessor;
import trieMatch.util.TOD;
import trieMatch.util.data.IO;

public class Query {

	final int keymatchFile = 0, testInputsfile = 1, outputFile = 2;
	PrintStream outstream;
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static String defaultArgstring = "c:/temp/matchtext.base.031 $in - -exp";
	String filler = String.format("%105s | ", "");
	int nominalLength = 92;
	boolean _useDP = false;
	boolean _echo = false; // echo match inputs?
	boolean SHOWALL = true; // show all results, or just show the top result?
	String aggregatorName = Constants.DEFAULT_AGGREGATOR_NAME;
	String aggregatorParm = Constants.DEFAULT_AGGREGATOR_PARM;

	MatcherActionDefinition resultRequest = MatcherActionDefinition.longest;
	String kmFilename, queryFilename, outputName;
	EvalType evalType;
	static int numTestcols = 2;
	static int testQueryCol = 1;
	static int testResponseCol = 0;
	boolean usesSymbology = false;
	boolean echo;

	ResultFormatter resultFormatter = new ResultFormatter();

	public static void main(final String[] args) {
		List<String> res = null;
		System.out.println(TOD.now() + ": EXECUTION BEGINS");
		final Query query = new Query();
		final LocalStore localStore = query.new LocalStore(args);
		while ((res = localStore.runMatch()) != null) {
			for (final String s : res) {
				System.out.println(s + " " + localStore.kmPrimary.getDescription(s.substring(0, s.lastIndexOf(' '))));
			}
		}
		System.out.println(TOD.now() + ": EXECUTION COMPLETE");
		System.exit(0);
	}

	public class LocalStore {
		KeyMatch kmPrimary;
		TrieMatcher trieMatcher;
		String[] queryFilenames;
		Boolean readEval = false;

		LocalStore() {
		}

		LocalStore(final String[] args) {
			this();
			processArgs(args);
			if (queryFilename == null || queryFilename.equals("$in")) {
				readEval = true;
			}
			queryFilenames = queryFilename.split(" +");
			Tiers.mapFactoryInstance = resultRequest.getMapFactory();
			TrieMatcher.setAggregator(aggregatorName, aggregatorParm);
			System.out.println(TOD.now() + ": Start reading keymatches from " + kmFilename);
			try {
				kmPrimary = new KeyMatch(kmFilename);
			} catch (final IOException e) {
				e.printStackTrace();
			}
			System.out.println(TOD.now() + ": read " + KeyMatch.symbolToName.size() + " lines.");
			trieMatcher = resultRequest.select(kmPrimary.tiers, kmPrimary.elementIndexer);
		}

		void setFilenames(final String... names) {
			queryFilenames = names;
		}

		List<String> runMatch(final String... querie) {
			if (readEval) {
				final List<String> res = new ArrayList<String>();
				final List<String> r = readEval(this);
				if (r == null) { return null; }
				res.addAll(r);
				return res;
			}
			final ItemProcessorIF<String> queryItemProcessor = new QueryItemProcessor(this);
			final FileProcessor fileProcessor = new LabelledFileProcessor(queryItemProcessor);
			fileProcessor.process(queryFilenames);
			return null;
		}

		class LabelledFileProcessor extends FileProcessor {
			public LabelledFileProcessor(final ItemProcessorIF<String> queryItemProcessor) {
				super(queryItemProcessor);
			}

			@Override
			protected void processItems(final File f) {
				String fn = null;
				try {
					fn = f.getCanonicalPath();
					System.out.println(TOD.now() + ": Begin reading queries from " + fn);
					final List<String> data = IO.readInput(f);
					System.out.println(TOD.now() + ": Begin matching for " + data.size() + " queries of " + fn);
					final long t0 = System.currentTimeMillis();
					processQuerylist(data);
					final long t1 = System.currentTimeMillis();
					final long totalTime = t1 - t0;
					showStats(totalTime, data.size());
				} catch (final Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	class QueryItemProcessor implements ItemProcessorIF<String> {
		LocalStore localStore;
		PrintStream ostream = null;
		ResultFormatter resultFormatter = new ResultFormatter();

		QueryItemProcessor(final LocalStore localStore) {
			this.localStore = localStore;
			this.ostream = outstream;
		}

		QueryItemProcessor(final LocalStore localStore, final Object outstream) {
			this.localStore = localStore;
			this.ostream = (PrintStream) outstream;
		}

		public Object processItem(final String query) {
			final List<String> ans = eval(localStore, query);
			if (outstream != null) {
				for (final String s : ans) {
					ostream.printf("%50s ==> %s %s\n", query, s, localStore.kmPrimary.getDescription(s.substring(0, s
							.lastIndexOf(' '))));
					if (!SHOWALL) {
						break;
					}
				}
			}
			return null;
		}
	}

	// public void showStats(final long mtime, final int nQueries) {
	// System.out.println(TOD.now() + ": Issued total of " + nQueries + " requests");
	// System.out.println(TOD.now() + ": " + "'single'" + " thread; " + "all" + " queries/batch; " + "one" + " batch
	// generated, "
	// + "one" + " started, " + "one" + " returned, " + "one" + " completed.");
	// System.out.println(TOD.now() + ": ::SUMMARY:: " + "queryRate=" + (nQueries / (double) mtime) + " qpms"
	// + "; queriesProcessed=" + nQueries + "; showResults=" + (outstream == null
	// ? "suppressed"
	// : "enabled") + "; timeElapsed=" + mtime + " ms" + "; timeUtilized=" + mtime + " ms" + "; numThreads="
	// + "'one'" + "; chunkSize=" + "all" + " queries" + "; numInputs=" + nQueries + "; numIter=" + "one"
	// + "; numThread=" + "'one'" + "; aggregator=" + aggregatorName + (aggregatorParm != null
	// ? ("(" + aggregatorParm + ")")
	// : "") + "; searchFlavor=" + resultRequest + "; keymatchFile=\"" + kmFilename + "\"; queryFile=\""
	// + queryFilename + "\"");
	// }

	public void showArgs(final String info) {
		System.out.println(TOD.now() + ": Search flavor   " + resultRequest);
		System.out.println(TOD.now() + ": Aggregator name " + aggregatorName);
		System.out.println(TOD.now() + ": Aggregator parm " + aggregatorParm);
		try {
			System.out.println(TOD.now() + ": Keymatches      " + (new File(kmFilename)).getCanonicalPath());
			System.out.println(TOD.now() + ": Test input      " + (new File(queryFilename)).getCanonicalPath());
			String fname;
			if (outputName == null) {
				fname = "not printed";
			} else if (outputName.compareTo("-") == 0) {
				fname = "console";
			} else {
				fname = (new File(outputName)).getCanonicalPath();
			}
			System.out.println(TOD.now() + ": Results out     " + fname);

		} catch (final IOException e) {
			e.printStackTrace();
		}
	}

	public void showStats(final long mtime, final int nQueries) {
		System.out.println("\n" + TOD.now() + ": Execution complete");
		showArgs();
		System.out.println(TOD.now() + ": Total queries   " + nQueries);
		System.out.println(TOD.now() + ": Elapsed (ms)    " + mtime);
		System.out.println(TOD.now() + ": Queries / ms    " + (nQueries / (double) mtime));
		System.out.println(TOD.now() + ": Ms / query      " + ((double) mtime / ((double) nQueries)));
		System.out.println();
	}

	public List<String> readEval(final LocalStore localStore) {
		System.out.println("?");
		final String queryText = getLine().trim();
		if (queryText.length() == 0) { return null; }
		return eval(localStore, queryText);
	}

	public List<String> eval(final LocalStore localStore, final String queryText) {
		// return localStore.kmPrimary.processQuery(localStore.trieMatcher, queryText);
		final List<KMDefinition> ans = localStore.trieMatcher.findMatch(queryText);
		final List<String> rans = resultFormatter.formatResults(localStore.trieMatcher.reduce(ans));
		return rans;
	}

	public String getLine() {
		String s = null;
		try {
			while (!in.ready()) {
				Thread.sleep(100);
			}
			s = in.readLine();
			return s;
		} catch (final Exception e) {
			e.printStackTrace();
		}
		return s;
	}

	static String getQueryPart(final String[] line) {
		if (testQueryCol < line.length) { return line[testQueryCol]; }
		return null;
	}

	static String getResponsePart(final String[] line) {
		if (testResponseCol < line.length) { return line[testResponseCol]; }
		return null;
	}

	public void usage() {
		System.err.println("usage: java -jar file.jar [options] keymatchFilename testinputsFilename [outputFilename]");
		System.err
				.println("       options: -all/-longest/-top/-exp       -echo/-noEcho    -deleteOutputFile  -preExpand  -permuteExpand");
		System.err.println("                -nArg #");
		System.err.println("%n       the -exp gives \"experimental\" prefix matching and supports \"lookahead\".");
		System.err.println("%n       For interactive input use: java -Xmx1540M -jar c:\\mlrus\\kmQ.jar 5000.input $in - -all");
		System.exit(0);
	}

	public void processArgs(final String[] args) {
		boolean deleteOutputfile = false;
		final List<String> filenames = new ArrayList<String>();
		evalType = EvalType.eINP;
		boolean grab_numTestcols = false;
		for (int i = 0; i < args.length; i++) {
			if (args[i].equalsIgnoreCase("-h")) {
				usage();
			} else if (grab_numTestcols) {
				numTestcols = Integer.parseInt(args[i]);
				grab_numTestcols = false;
				System.out.println("Expect " + numTestcols + " of input per test line");
			} else if (args[i].equalsIgnoreCase("-aggName")) {
				aggregatorName = args[++i];
			} else if (args[i].equalsIgnoreCase("-aggParm")) {
				aggregatorParm = args[++i];
			} else if (args[i].equalsIgnoreCase("-all")) {
				resultRequest = MatcherActionDefinition.all;
			} else if (args[i].equalsIgnoreCase("-longest")) {
				resultRequest = MatcherActionDefinition.longest;
			} else if (args[i].equalsIgnoreCase("-top")) {
				resultRequest = MatcherActionDefinition.top;
			} else if (args[i].equalsIgnoreCase("-exp")) {
				resultRequest = MatcherActionDefinition.exp; // EXPERIMENTAL !!
			} else if (args[i].equalsIgnoreCase("-echo")) {
				echo = true;
			} else if (args[i].equalsIgnoreCase("-noEcho")) {
				echo = false;
			} else if (args[i].equalsIgnoreCase("-nArg")) {
				grab_numTestcols = true;
			} else if (args[i].equalsIgnoreCase("-deleteOutputFile")) {
				deleteOutputfile = true;
			} else {
				filenames.add(args[i]);
			}
		}
		filenames.add(null);
		if (filenames.size() < 2) {
			usage();
			System.exit(0);
		}
		kmFilename = filenames.get(keymatchFile);
		queryFilename = filenames.get(testInputsfile);
		outputName = filenames.get(outputFile);
		checkOutputfile(outputName, deleteOutputfile);
		try {
			outstream = (outputName != null)
					? safePrintStream(outputName)
					: null;
		} catch (final FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	public void showArgs() {
		try {
			System.out.println(TOD.now() + ": Keymatches from " + (new File(kmFilename).getCanonicalPath()));
		} catch (final IOException e) {
			e.printStackTrace();
		}
		System.out.println(TOD.now() + ": Aggregator name " + aggregatorName);
		System.out.println(TOD.now() + ": Aggregator parm " + aggregatorParm);
		System.out.println(TOD.now() + ": Test input from " + queryFilename);
		System.out.println(TOD.now() + ": Results outputs " + (outputName == null
				? "not printed"
				: outputName));
		System.out.println(TOD.now() + ": Results outputs " + resultRequest + " result.");
		System.out.println(TOD.now() + ": Compute evalType " + evalType);
	}

	public void showConfiguration(final KeyMatch kmPrimary, final List<String> queryList) {
		if (echo) {
			System.out.println("Rules:");
			kmPrimary.tiers.printTiers(System.out);
		}
	}

	public String showAndSetFmt(final String query, final String tkr, final int size) {
		outstream.print("%nName:" + query + ".");
		if (tkr != null) {
			outstream.print(" \tSymbol: \"" + tkr + "\".");
		}
		outstream.println(" \tSubquery count: " + size + ".");
		return String.format("%%%ds | dEdit=%%-2d | ", nominalLength - query.length() - (tkr != null
				? tkr.length()
				: 0));
	}

	public static boolean checkOutputfile(final String name, final boolean deletableOutputfile) {
		if (stdFilename(name)) { return false; }
		final File f = new File(name);
		if (!f.exists() || (deletableOutputfile && f.isFile() && f.delete())) { return true; }
		return false;
	}

	static boolean stdFilename(final String filename) {
		return filename == null || filename.length() == 0 || filename.equals("-");
	}

	PrintStream fileFoundHandler(final String s) throws FileNotFoundException {
		throw new FileNotFoundException("File exists: " + s);
	}

	public BufferedReader openInput(final String filename) throws Exception {
		return new BufferedReader((stdFilename(filename)
				? (new InputStreamReader(System.in))
				: (new FileReader(filename))));
	}

	public PrintStream safePrintStream() throws FileNotFoundException {
		return safePrintStream(null);
	}

	public PrintStream safePrintStream(final String filename) throws FileNotFoundException {
		return stdFilename(filename)
				? (new PrintStream(System.out))
				: ((!(new File(filename)).exists())
						? (new PrintStream(filename))
						: fileFoundHandler(filename));
	}
}
